In [1]:
import pandas as pd
import itable
import ffn
import talib
%matplotlib inline
def side_by_side(*objs, **kwds):
from pandas.formats.printing import adjoin
space = kwds.get('space', 4)
reprs = [repr(obj).split('\n') for obj in objs]
print (adjoin(space, *reprs))
import os
os.chdir('C:\\users\\Dave\\pycharmprojects\\simplebacktester')
os.getcwd()
Out[1]:
In [31]:
import pandas as pd
import ffn
%matplotlib inline
from backtest_helpers.compute_weights_RS_DM import compute_weights_RS_DM
from backtest_helpers.compute_weights_PMA import compute_weights_PMA
from backtest_helpers.monthly_return_table import monthly_return_table
from backtest_helpers.endpoints import endpoints
from backtest_helpers.backtest import backtest
strategies = {
'RS0001': { 'symbols': ['VCVSX','VWEHX','VFIIX','FGOVX','VWAHX'], 'prices': 'yahoo',
'rs_lookback': 1, 'risk_lookback': 1, 'n_top': 2, 'frequency': 'M',
'cash_proxy': 'CASHX', 'risk_free': 0},
'RS0002': {'symbols': ['MMHYX','FAGIX','VFIIX'], 'prices': 'yahoo',
'rs_lookback': 3, 'risk_lookback': 2, 'n_top': 1, 'frequency': 'M',
'cash_proxy': 'CASHX', 'risk_free': 0},
'RS0003': {'symbols': ['MMHYX','FAGIX','VFIIX'], 'prices': 'yahoo',
'rs_lookback': 1, 'risk_lookback': 1, 'n_top': 1, 'frequency': 'Q',
'cash_proxy': 'CASHX', 'risk_free': 0},
'DM0001': {'symbols': ['VCVSX','VWINX','VWEHX','VGHCX','VUSTX','VFIIX','VWAHX','FGOVX','FFXSX'],
'prices': 'yahoo',
'rs_lookback': 1, 'risk_lookback': 1, 'n_top': 3, 'frequency': 'M',
'cash_proxy': 'CASHX', 'risk_free': 'FFXSX'},
'DM0002': {'symbols': ['VCVSX','VUSTX','VWEHX','VFIIX','VGHCX','FRESX'], 'prices': 'yahoo',
'rs_lookback': 1, 'risk_lookback': 1, 'n_top': 5, 'frequency': 'M',
'cash_proxy': 'VFIIX', 'risk_free': 'FFXSX'},
'PMA001': {'symbols': ['VCVSX', 'VFIIX'], 'prices': 'yahoo',
'risk_lookback': 3, 'frequency': 'M', 'allocations': [0.6, 0.4],
'cash_proxy': 'VUSTX'},
'PMA002': {'symbols': ['VCVSX', 'VWINX', 'VWEHX'], 'prices': 'yahoo',
'risk_lookback': 3, 'frequency': 'M', 'allocations': [0.6, 0.2, 0.2],
'cash_proxy': 'VUSTX'},
'PMA003': {'symbols': ['VCVSX', 'FAGIX', 'VGHCX'], 'prices': 'yahoo',
'risk_lookback': 2, 'frequency': 'M', 'allocations': [1./3., 1./3., 1./3.],
'cash_proxy': 'VUSTX'},
}
strategy_values = pd.DataFrame(columns=strategies.keys())
security_weights = {}
security_holdings = {}
security_prices = {}
for name in strategies :
if 'PMA' in name :
s_value, s_holdings, s_weights, s_prices = compute_weights_PMA (name, strategies[name])
else :
s_value, s_holdings, s_weights, s_prices = compute_weights_RS_DM (name, strategies[name])
strategy_values[name] = s_value
security_weights[name] = s_weights
security_holdings[name] = s_holdings
security_prices[name] = s_prices
In [32]:
pd.Panel(security_weights)
Out[32]:
In [58]:
# save results so don't have to recalculate
strategy_values.to_pickle('C:\\Notebooks\\backtesting\\strategy_values.pkl')
pd.Panel(security_weights).to_pickle('C:\\Notebooks\\backtesting\\security_weights.pkl')
pd.Panel(security_holdings).to_pickle('C:\\Notebooks\\backtesting\\security_holdings.pkl')
pd.Panel(security_prices).to_pickle('C:\\Notebooks\\backtesting\\security_prices.pkl')
# strategy_values = pd.read_pickle('C:\\Notebooks\\backtesting\\strategy_values.pkl')
# security_weights = pd.read_pickle('C:\\Notebooks\\backtesting\\security_weights.pkl').fillna(0)
# security_holdings = pd.read_pickle('C:\\Notebooks\\backtesting\\security_holdings.pkl').fillna(0)
# security_prices = pd.read_pickle('C:\\Notebooks\\backtesting\\security_prices.pkl').fillna(0)
In [61]:
s_weights = dict()
s_holdings = dict()
s_prices = dict()
for f in ['security_weights', 'security_holdings', 'security_prices']:
for name in strategies:
df = pd.read_pickle('C:\\Notebooks\\backtesting\\' + f + '.pkl')[name]
index = (df.sum() != 0)
if f == 'security_weights':
s_weights[name] = df[index[index==True].index]
if f == 'security_holdings':
s_holdings[name] = df[index[index==True].index]
if f == 'security_prices':
s_prices[name] = df[index[index==True].index]
In [68]:
strategy_values = pd.read_pickle('C:\\Notebooks\\backtesting\\strategy_values.pkl')
security_weights = s_weights.copy()
security_holdings = s_holdings.copy()
security_prices = s_prices.copy()
In [69]:
name = 'RS0001'
strategy_values[name].plot(figsize=(15, 10), grid=True)
Out[69]:
In [70]:
index = strategy_values.dropna().index
rebalance_dates = endpoints(period='M', trading_days=index)
# find the set of all portfolio symbols
n = len(strategies)
l = [list(security_weights[name].columns) for name in strategies]
s = []
for i in range(n) :
s = s + l[i]
aggregated_weights = pd.DataFrame(0, index=rebalance_dates, columns=list(set(s)))
all_prices = pd.DataFrame(0, index=index, columns=list(set(s)))
# for equally weighted strategies
strategy_weights = pd.Series([1. / n for i in range(n)], index=list(strategies.keys()))
prices = security_prices.copy()
for name in strategies :
aggregated_weights[security_weights[name].columns] += security_weights[name].loc[rebalance_dates] * strategy_weights[name]
all_prices = prices[name].loc[index].combine_first(all_prices)
In [71]:
all_prices[:15]
Out[71]:
In [72]:
aggregated_weights.round(3)[:15]
Out[72]:
In [73]:
# equally weighted
from backtest_helpers.backtest import backtest
p_value, p_holdings, p_weights = backtest(all_prices, aggregated_weights, 10000., offset=0, commission=10.)
p_value.plot(figsize=(15, 10), grid=True)
Out[73]:
In [74]:
ffn.calc_perf_stats(p_value).display()
In [75]:
strategy_prices = strategy_values.dropna().copy()
strategy_prices[:3]
Out[75]:
In [76]:
# need to add prices for cash_proxy and, if necessary, risk_free
from backtest_helpers.get_yahoo_data import get_yahoo_data
strategy_prices['FFXSX'] = get_yahoo_data(['FFXSX']).loc[index]
strategy_prices[:1]
Out[76]:
In [78]:
strategies1 = {
'MUTLTI-RS': { 'symbols': list(strategies.keys()), 'prices': strategy_prices,
'rs_lookback': 1, 'risk_lookback': 1, 'n_top': 8, 'frequency': 'M',
'cash_proxy': 'FFXSX', 'risk_free': 0}}
for name in strategies1 :
s_value, s_holdings, s_weights, s_prices = compute_weights_RS_DM (name, strategies1[name])
In [79]:
s_value.plot(figsize=(15, 10), grid=True)
Out[79]:
In [80]:
# poorer return but lower drawdown and better SR
ffn.calc_perf_stats(s_value).display()
In [81]:
def highlight_pos_neg (s) :
is_positive = s > 0
return ['background-color : rgb(127,255,0)' if v else 'background-color : rgb(255,99,71)' for v in is_positive]
df = monthly_return_table (s_value)
df.style.\
apply(highlight_pos_neg)
Out[81]:
In [82]:
frame = df['Annual Returns'].to_frame()
frame['positive'] = df['Annual Returns'] >= 0
frame['Annual Returns'].plot(figsize=(15,10),kind='bar',color=frame.positive.map({True: 'g', False: 'r'}), grid=True)
Out[82]:
In [83]:
s_weights.loc[rebalance_dates].round(3)[:15]
Out[83]:
In [84]:
# get weights from backtest
strategy_weights = s_weights.loc[rebalance_dates].copy()
strategy_weights.round(3)[:15]
Out[84]:
In [85]:
aggregated_weights = pd.DataFrame(0, index=rebalance_dates, columns=list(set(s + ['FFXSX'])))
all_prices = pd.DataFrame(0, index=index, columns=list(set(s + ['FFXSX'])))
# get weights from backtest
strategy_weights = s_weights.loc[rebalance_dates].copy()
prices = security_prices.copy()
for name in strategies :
aggregated_weights[security_weights[name].columns] += security_weights[name].loc[rebalance_dates].mul(strategy_weights[name],axis=0)
all_prices = prices[name].loc[index].combine_first(all_prices)
# need to add in the cash_proxy weights
aggregated_weights['FFXSX'] = aggregated_weights['FFXSX'].add(strategy_weights['FFXSX'], axis=0)
aggregated_weights.round(3)[:15]
Out[85]:
In [86]:
all_prices[:15]
Out[86]:
In [87]:
from backtest_helpers.backtest import backtest
aggregated_weights = aggregated_weights[aggregated_weights.sum(1) > 0]
values, holdings, weights = backtest(all_prices, aggregated_weights, 10000., offset=0, commission=10.)
In [88]:
# just to check nothing changed
values.plot(figsize=(15, 10), grid=True)
Out[88]:
In [89]:
transactions = (holdings - holdings.shift(1).fillna(0))
transactions = transactions[transactions.sum(1) != 0]
transactions.round(0)[:15]
Out[89]:
In [90]:
def generate_orders(transactions, prices) :
orders = pd.DataFrame()
for i in range(len(transactions)):
for j in range(len(transactions.columns)):
t = transactions.ix[i]
qty = abs(t[j])
if qty >= 1.:
if transactions.ix[i][j] < 0 :
orders = orders.append([[t.name.date().year, t.name.date().month, t.name.date().day, t.index[j],\
'Sell', -abs(t[j]), prices.ix[t.name][t.index[j]]]])
if transactions.ix[i][j] > 0 :
orders = orders.append([[t.name.date().year, t.name.date().month, t.name.date().day, t.index[j],\
'Buy', abs(t[j]), prices.ix[t.name][t.index[j]]]])
orders.columns = ['Year', 'Month', 'Day', 'Symbol', 'Action', 'Qty', 'Price']
orders
return orders
In [91]:
# del transactions['CASHX']
orders = generate_orders(transactions, all_prices)
orders[:10]
Out[91]:
In [ ]: